<?php
###
#  Baing PHP 框架 1.0.0
#  http://baing.co/
#
#  Copyright: 白晓阳
#  License: CC BY-NC-SA 4.0
#  https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
###

namespace bai;

use bai\feature\RequestException;
use bai\feature\Paths;
use bai\resource\document\Cache;


abstract class Process extends Baing
{
    use \bai\feature\CacheFileStatic;

    protected const PATH = '*';

    protected static array $getters = ['path', 'view', 'params', 'resource'];

    private static array $map = [];

    protected array $actions = [];

    final public static function setup(?string $path): self
    {
        $runtime = [];
        $name = self::className(self::class);
        $config = self::config($name);

        # index process
        $indexProcess = $config['index'] ?? 'bai\process\Index';
        if ($path == null || $path === '/') {
            $runtime['path'] = '/';
            return Baing::loadClass($indexProcess, $runtime, static::class);
        }
        $runtime['path'] = $path;

        # view process
        if (preg_match('/\\.([a-zA-Z0-9]+)$/', $path, $matches)) {
            $runtime['view'] = $view = strtolower($matches[1]);
            if ($processName = $config['map'][".$view"] ?? '') {
                return Baing::loadClass($processName, $runtime, static::class);
            }
            $process = Baing::loadClass(ucfirst($view), $runtime, static::class);
            return $process ?? Baing::loadClass($indexProcess, $runtime, static::class);
        }

        // custom process
        $method = self::$request->method;
        $processes = $config['processes'] ?? [];
        foreach ($processes as $pattern => $def) {
            $params = Paths::mapPath($path, $pattern);
            if ($params === null) continue;
            self::$request->runtime = $runtime['params'] = $params;
            if ($extends = $def['extends'] ?? '') {
                return Baing::loadClass($extends, array_replace_recursive($runtime, $def[$method] ?? []), static::class);
            }
            $processName = $config['custom'] ?? 'bai\Process\Custom';
            return Baing::loadClass($processName, array_replace_recursive($runtime, $def[$method] ?? []), static::class);
        }

        // map process
        if (!self::$map) {
            self::$map = self::loadCacheFile(self::class);
            if ($map = self::config([$name, 'map'])) {
                self::$map = array_replace(self::$map, $map);
            }
        }
        foreach (self::$map as $pattern => $processName) {
            $params = Paths::mapPath($path, $pattern);
            if ($params === null) continue;
            self::$request->runtime = $runtime['params'] = $params;
            return Baing::loadClass($processName, $runtime, static::class);
        }

        return Baing::loadClass($indexProcess, $runtime, static::class);
    }

    private static function refreshCacheFile(string $path)
    {
        $items = [];
        $filter = '\\' . lcfirst(self::className(self::class)) . '\\';
        $classMap = self::config('autoload.map') ?? [];
        foreach ($classMap as $className => $file) {
            if (!str_contains($className, $filter) || !is_subclass_of($className, self::class)) continue;
            if (!($processPath = $className::PATH)) continue;
            if (is_string($processPath)) {
                $items[self::preparePath($processPath, $className, $filter)] = $className;
                continue;
            }
            foreach ($processPath as $value) {
                $items[self::preparePath($value, $className, $filter)] = $className;
            }
        }
        uksort($items, [Paths::class, 'sortPath']);

        Cache::refresh($path, $items);
        return $items;
    }

    private static function preparePath(string $path, string $process, string $filter): string
    {
        if ($path !== '*') return $path;
        $pieces = explode($filter, $process);
        $path = $pieces[1] ?? '';
        $path = preg_replace_callback('/(?<=[a-z0-9])[A-Z]/', fn($matchs) => '-' . strtolower($matchs[0]), $path);
        $path = preg_replace_callback('/[A-Z]/', fn($matchs) => strtolower($matchs[0]), $path);
        $path = '/' . str_replace('\\', '/', $path);
        return $path;
    }

    public function run()
    {
        self::$logger->info('-- Process Start: ' . static::class);
        $method = self::$request->method;
        $call = strtolower($method);
        if (method_exists($this, $call) && ($actions = $this->$call())) {
            $this->loadActions($actions);
        }
        $this->runActions();
        self::$logger->info('-- Process End: ' . static::class);
    }

    protected function init()
    {
        if ($params = $this->find('params')) {
            foreach ($params as $key => $param) {
                if (isset($this->runtime[$key])) continue;
                $this->runtime[$key] = $param;
            }
        }
        if (!$this->find('solo') && ($actions = self::config([self::className(self::class), 'actions']))) {
            $this->loadActions($actions);
        }
        if ($actions = $this->find('actions')) {
            $this->loadActions($actions);
        }
    }

    protected function get(): array
    {
        throw new RequestException(code: Response::STATUS_BAD_REQUEST);
    }

    protected function post(): array
    {
        throw new RequestException(code: Response::STATUS_BAD_REQUEST);
    }

    protected function patch(): array
    {
        throw new RequestException(code: Response::STATUS_BAD_REQUEST);
    }

    protected function delete(): array
    {
        throw new RequestException(code: Response::STATUS_BAD_REQUEST);
    }

    protected function loadAction(string $action, array $runtime = [], int $order = Action::ORDER_NORMAL)
    {
        if (!$action) return;
        $this->actions[] = [
            'action' => $action,
            'runtime' => $runtime,
            'order' => $order,
        ];
    }

    protected function loadActions(array $actions)
    {
        foreach($actions as $action) {
            if (!isset($action['action'])) continue;
            if (!isset($action['order'])) {
                $action['order'] = Action::ORDER_NORMAL;
            }
            $this->actions[] = $action;
        }
    }

    protected function runActions()
    {
        usort($this->actions, fn($a, $b) => ($a['order'] <= $b['order'] ? -1 : 1));
        $status = Action::SUCCEEDED;
        foreach ($this->actions as $action) {
            if ($status === Action::FAILED && ($action['runtime']['persistent'] ?? false)) {
                continue;
            }

            $actionName = $action['action'];
            $actionRuntime = $action['runtime'] ?? [];
            self::$logger->info("-- Action Start: $actionName");
            if ($actionRuntime) {
                self::$logger->info('-- Action runtime: ' . json_encode($action['runtime']));
            }

            $entry = self::loadClass($actionName, $actionRuntime, 'bai\Action');
            $status = $entry->run();

            self::$logger->info("-- Action End: $actionName");
        }
    }
}
